Search K
Appearance
Appearance
在面试的过程中,经常会被问到:" 你可以讲讲三次握手、四次挥手吗?",大部分面试者都会熟练的背诵,每个阶段做什么,这篇文章我们将深入讲解连接终止相关的细节问题。
最常见的四次挥手的过程下图所示

客户端调用 close 方法,执行「主动关闭」,会发送一个 FIN 报文给服务端,从这以后客户端不能再发送数据给服务端了,客户端进入 FIN-WAIT-1 状态。FIN 报文其实就是将 FIN 标志位设置为 1。

FIN 段是可以携带数据的,比如客户端可以在它最后要发送的数据块可以 " 捎带 "FIN 段。当然也可以不携带数据。不管 FIN 段是否携带数据,都需要消耗一个序列号。
客户端发送 FIN 包以后不能再发送数据给服务端,但是还可以接受服务端发送的数据。这个状态就是所谓的「半关闭(half-close)」
主动发起关闭的一方称为「主动关闭方」,另外一段称为「被动关闭方」。
服务端收到 FIN 包以后回复确认 ACK 报文给客户端,服务端进入 CLOSE_WAIT,客户端收到 ACK 以后进入 FIN-WAIT-2 状态。
服务端也没有数据要发送了,发送 FIN 报文给客户端,然后进入 LAST-ACK 状态,等待客户端的 ACK。同前面一样如果 FIN 段没有携带数据,也需要消耗一个序列号。
客户端收到服务端的 FIN 报文以后,回复 ACK 报文用来确认第三步里的 FIN 报文,进入 TIME_WAIT 状态,等待 2 个 MSL 以后进入 CLOSED 状态。服务端收到 ACK 以后进入 CLOSED 状态。TIME_WAIT 是一个很神奇的状态,后面有文章会专门介绍。
如三次握手的 SYN 报文一样,不管是否携带数据,FIN 段都需要消耗一个序列号。我们用一个图来解释,如果 FIN 段不消耗一个序列号会发生什么。

如上图所示,如果 FIN 包不消耗一个序列号。客户端发送了 100 字节的数据包和 FIN 包,都等待服务端确认。如果这个时候客户端收到了 ACK=1000 的确认包,就无法得知到底是 100 字节的确认包还是 FIN 包的确认包。
首先我们先明确一个问题,TCP 连接终止一定要四次包交互吗?三次可以吗?
当然可以,因为有延迟确认的存在,把第二步的 ACK 经常会跟随第三步的 FIN 包一起捎带会对端。延迟确认后面有一节专门介绍。

一个真实的 wireshark 抓包如下图所示

其实这个行为跟应用层有比较大的关系,因为发送 FIN 包以后,会进入半关闭(half-close)状态,表示自己不会再给对方发送数据了。因此如果服务端收到客户端发送的 FIN 包以后,只能表示客户端不会再给自己发送数据了,但是服务端这个时候是可以给客户端发送数据的。
在这种情况下,如果不及时发送 ACK 包,死等服务端这边发送数据,可能会造成客户端不必要的重发 FIN 包,如下图所示。

如果服务端确定没有什么数据需要发给客户端,那么当然是可以把 FIN 和 ACK 合并成一个包,四次挥手的过程就成了三次。
其实理论上完全是可以的,把三次握手的第二次的 SYN+ACK 拆成先回 ACK 包,再发 SYN 包就变成了「四次握手」

与 FIN 包不同的是,一般情况下,SYN 包都不携带数据,收到客户端的 SYN 包以后不用等待,可以立马回复 SYN+ACK,四次握手理论上可行,但是现实中我还没有见过。
前面介绍的都是一端收到了对端的 FIN,然后回复 ACK,随后发送自己的 FIN,等待对端的 ACK。TCP 是全双工的,当然可以两端同时发起 FIN 包。如下图所示

以客户端为例
FIN 包,等待对端对这个 FIN 包的 ACK,随后进入 FIN-WAIT-1 状态FIN-WAIT-1 状态的客户端还没有等到 ACK,收到了服务端发过来的 FIN 包CLOSING 状态CLOSING 状态的客户端终于等到了 ACK,随后进入 TIME-WAITTIME-WAIT 状态持续 2*MSL,进入 CLOSED 状态我用 packetdrill 脚本模拟了一下同时关闭,部分代码如下,完整的代码见:simultaneous-close.pkt
// 服务端发送 FIN
0.150 close(4) = 0
0.150 > F. 1:1(0) ack 1 <...>
// 客户端发送 FIN
0.150 < F. 1:1(0) ack 2 win 65535
// 服务端回复 ACK
0.150 > . 2:2(0) ack 2 <...>
// 客户端回复 ACK
0.150 < . 2:2(0) ack 2 win 65535使用 netstat 查看连接状态,可以看到两端都进入了 TIME_WAIT 状态
netstat -tnpa | grep -i 8080
tcp 0 0 192.168.198.228:8080 0.0.0.0:* LISTEN -
tcp 0 0 192.168.198.228:8080 192.0.2.1:35769 TIME_WAIT -
tcp 0 0 192.168.220.28:8080 192.0.2.1:35780 TIME_WAIT -使用 wireshark 抓包如下图所示,完整的抓包文件可以在这里下载:simultaneous-close.pcap

当然上面的脚本并不能每次模拟出两端都进入 TIME_WAIT 的状态,取决于在发送 FIN 包之前有没有提前收到对端的 FIN 包。如果在发送 FIN 之前收到了对端的 FIN,只会有一段进入 TIME_WAIT
这篇文章介绍了四次挥手断开连接的细节,然后用图解的方式介绍了为什么 FIN 包需要占用一个序列号。随后引出了为什么挥手要四次的问题,最后通过 packetdrill 的方式模拟了同时关闭。
HTTP 传输完成,断开进行四次挥手,第二次挥手的时候客户端所处的状态是:
正常的 TCP 三次握手和四次挥手过程(客户端建连、断连)中,以下状态分别处于服务端和客户端描述正确的是